package com.opencee.common.exception;

import com.opencee.common.constants.ResultCode;
import com.opencee.common.model.ApiErrorResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.support.DefaultMessageSourceResolvable;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.oauth2.common.exceptions.OAuth2Exception;
import org.springframework.security.web.util.ThrowableAnalyzer;
import org.springframework.validation.BindException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.util.stream.Collectors;

/**
 * 统一Web异常处理器
 *
 * @author LYD
 * @date 2017/7/3
 */
@ControllerAdvice
@ResponseBody
@Slf4j
@Order(Ordered.HIGHEST_PRECEDENCE)
public class DefaultWebExceptionAdvice {
    private static final String NO_HANDLER_FOUND_EXCEPTION = "NoHandlerFoundException";
    private static final String HTTP_REQUEST_METHOD_NOT_SUPPORTED_EXCEPTION = "HttpRequestMethodNotSupportedException";
    private static final String HTTP_MEDIA_TYPE_NOT_SUPPORTED_EXCEPTION = "HttpMediaTypeNotSupportedException";
    private static final String MISSING_SERVLET_REQUEST_PARAMETER_EXCEPTION = "org.springframework.web.bind.MissingServletRequestParameterException";
    private static final String BIND_EXCEPTION = "org.springframework.validation.BindException";
    private static final String CONSTRAINT_VIOLATION_EXCEPTION = "javax.validation.ConstraintViolationException";
    private static final String METHOD_ARGUMENT_NOT_VALID_EXCEPTION = "org.springframework.web.bind.MethodArgumentNotValidException";
    private static final String BAD_CREDENTIALS = "Bad credentials";
    private static final String USER_IS_DISABLED = "User is disabled";
    private static final String USER_ACCOUNT_IS_LOCKED = "User account is locked";
    private static final String USER_ACCOUNT_HAS_EXPIRED = "User account has expired";
    private static final String USER_CREDENTIALS_HAVE_EXPIRED = "User credentials have expired";

    private static ThrowableAnalyzer throwableAnalyzer = new ThrowableAnalyzer();

    /**
     * oauth2异常解析
     *
     * @param e       异常
     * @param request http请求
     */
    @ExceptionHandler(OAuth2Exception.class)
    public ResponseEntity<ApiErrorResult> handleOAuth2Exception(OAuth2Exception e, HttpServletRequest request) throws Exception {
        return resolveException(e, request.getRequestURI());
    }

    /**
     * 其他异常
     *
     * @param e        异常
     * @param request  http请求
     * @param response http响应
     */
    @ExceptionHandler({Exception.class})
    public ResponseEntity<ApiErrorResult> exception(Exception e, HttpServletRequest request, HttpServletResponse response) {
        return resolveException(e, request.getRequestURI());
    }


    /**
     * 静态解析异常。
     * 为了兼容 非 Servlet容器异常解析,使用异常类名.
     *
     * @param e    异常
     * @param path 请求路径
     */
    public static ResponseEntity<ApiErrorResult> resolveException(Exception e, String path) {
        ApiErrorResult error = ApiErrorResult.error(ResultCode.INTERNAL_SERVER_ERROR, e.getMessage());
        Throwable[] causeChain = throwableAnalyzer.determineCauseChain(e);
        String className = e.getClass().getName();
        if (className.contains(NO_HANDLER_FOUND_EXCEPTION)) {
            error.code(ResultCode.NOT_FOUND);
            return buildResponseEntity(e, error, path, false);
        }
        if (className.contains(HTTP_REQUEST_METHOD_NOT_SUPPORTED_EXCEPTION)) {
            error.code(ResultCode.METHOD_NOT_ALLOWED);
            return buildResponseEntity(e, error, path, false);
        }
        if (className.contains(HTTP_MEDIA_TYPE_NOT_SUPPORTED_EXCEPTION)) {
            error.code(ResultCode.MEDIA_TYPE_NOT_ACCEPTABLE);
            return buildResponseEntity(e, error, path, false);
        }
        if (MISSING_SERVLET_REQUEST_PARAMETER_EXCEPTION.equals(className)) {
            error.code(ResultCode.BAD_REQUEST).msg(e.getMessage());
            return buildResponseEntity(e, error, path, false);
        }
        if (BIND_EXCEPTION.equals(className)) {
            String message = ((BindException) e).getBindingResult().getAllErrors().stream().map(DefaultMessageSourceResolvable::getDefaultMessage).collect(Collectors.joining());
            error.code(ResultCode.BAD_REQUEST).msg(message);
            return buildResponseEntity(e, error, path, false);
        }

        if (CONSTRAINT_VIOLATION_EXCEPTION.equals(className)) {
            String message = ((ConstraintViolationException) e).getConstraintViolations().stream().map(ConstraintViolation::getMessage).collect(Collectors.joining());
            error.code(ResultCode.BAD_REQUEST).msg(message);
            return buildResponseEntity(e, error, path, false);
        }
        if (METHOD_ARGUMENT_NOT_VALID_EXCEPTION.equals(className)) {
            String message = ((MethodArgumentNotValidException) e).getBindingResult().getAllErrors().stream().map(DefaultMessageSourceResolvable::getDefaultMessage).collect(Collectors.joining());
            error.code(ResultCode.BAD_REQUEST).msg(message);
            return buildResponseEntity(e, error, path, false);
        }

        Exception ase = (IllegalArgumentException) throwableAnalyzer.getFirstThrowableOfType(IllegalArgumentException.class, causeChain);
        if (ase != null) {
            error.code(ResultCode.BAD_REQUEST).msg(e.getMessage());
            return buildResponseEntity(e, error, path, false);
        }

        // oauth2异常处理
        ase = (OAuth2Exception) throwableAnalyzer.getFirstThrowableOfType(OAuth2Exception.class, causeChain);
        if (ase != null) {
            error.code(ResultCode.UNAUTHORIZED);
            return buildResponseEntity(e, error, path, false);
        }

        // 认证错误异常处理
        ase = (AuthenticationException) throwableAnalyzer.getFirstThrowableOfType(AuthenticationException.class,
                causeChain);
        if (ase != null) {
            error.code(ResultCode.UNAUTHORIZED);
            return buildResponseEntity(e, error, path, false);
        }

        // 权限不足异常处理
        ase = (AccessDeniedException) throwableAnalyzer
                .getFirstThrowableOfType(AccessDeniedException.class, causeChain);
        if (ase != null) {
            error.code(ResultCode.ACCESS_DENIED);
            return buildResponseEntity(e, error, path, false);
        }

        // 签名错误
        ase = (BaseSignatureErrorException) throwableAnalyzer
                .getFirstThrowableOfType(BaseSignatureErrorException.class, causeChain);
        if (ase != null) {
            error.code(ResultCode.SIGNATURE_DENIED);
            return buildResponseEntity(e, error, path, false);
        }

        // 自定义Feign调用异常
        ase = (BaseFeignRpcErrorException) throwableAnalyzer
                .getFirstThrowableOfType(BaseFeignRpcErrorException.class, causeChain);
        if (ase != null) {
            BaseFeignRpcErrorException fail = ((BaseFeignRpcErrorException) ase);
            error.errorMsg(fail.getMessage()).code(fail.getCode()).msg(fail.getMessage());
            return buildResponseEntity(e, error, path, false);
        }

        // 自定义业务异常处理
        ase = (BaseFailException) throwableAnalyzer
                .getFirstThrowableOfType(BaseFailException.class, causeChain);
        if (ase != null) {
            BaseFailException fail = ((BaseFailException) ase);
            error.errorMsg(fail.getMessage()).code(fail.getCode()).msg(fail.getMessage());
            return buildResponseEntity(e, error, path, true);
        }
        return buildResponseEntity(e, error, path, false);
    }

    private static ResponseEntity<ApiErrorResult> buildResponseEntity(Exception ex, ApiErrorResult result, String path, boolean warn) {
        result.setPath(path);
        ResponseEntity responseEntity = ResponseEntity.status(warn ? HttpStatus.OK : ResultCode.valueOfCode(result.getCode()).getHttpStatus()).body(result);
        if (warn) {
            log.warn("异常解析: {},{}", responseEntity, ex.getMessage());
        } else {
            log.error("异常解析: {}", responseEntity, ex);
        }
        return responseEntity;
    }
}
